package chapter6;

import java.util.*;
import javax.swing.Timer;

public class Main {
    public static void main(String[] args) {
        IA ia; // 不能new一个接口实例，但是能声明接口变量
        var a = new A();
        if(a instanceof IA) { // 判断对象是否实现某个接口
            ia = a;
            System.out.println(ia.f1() + " " + ia.f2());
        }
        
        var bb = new B[]{new B(3), new B(1), new B(2)};
        Arrays.sort(bb); // sort虽然接受的是Object[] a，但要求每个元素实现Comparable接口，否则报错
        // 内部会把对象变量强转为Comparable<B>类型，然后调用x.compareTo(y)
        for(var b : bb) System.out.print(b.id + " "); // 1 2 3
        System.out.println();

        // a = a.clone(); // 错误。从Object继承的clone是protected的
        var bbb = bb.clone(); // 所有数组类型都有clone方法，返回全部元素的副本
        bbb[0].id = 4;

        // 使用lambda表达式在Java中传递代码块
        // 对于*只有一个抽象方法的接口*，需要这种接口的对象时，就可以提供一个lambda表达式。这种接口称为**函数式接口**
        // lambda表达式的语法：(参数列表) -> 代码块
        // lambda表达式的返回值类型由编译器推断
        // lambda表达式的参数类型可以省略，将从函数式接口的类型推断
        ia = () -> {return 0;}; // ia只有一个抽象方法
        Arrays.sort(bbb, (x, y) -> x.id - y.id); // 仅含一个表示式时，无需{}，表达式的结果将作为返回值
        for(var b : bbb) System.out.print(b.id + " "); // 2 3 4
        System.out.println("\n" + ia.f1()); // 0
        // lambda表达式只能转换为函数式接口，甚至不能赋值给Object变量。Java语言中没有函数类型，不像golang或C++可以用变量保存函数表达式

        IC ic;
        ID id;
        // **方法引用**。指示编译器生成一个函数式接口的实例，覆盖这个接口的抽象方法来调用给定的方法
        // 1. object::instanceMethod。方法直接作为lambda表达式
        ic = System.out::println; // 等价于 x -> System.out.println(x) 。lambda参数数量=方法参数数量
        ic.f("hi");
        // 2. Class::instanceMethod。方法的隐式参数，作为lambda表达式的第一个参数。（应该反过来讲，lambda的第一个参数作为调用者，其余的作为方法参数）
        id = String::concat; // 等价于 (x, y) -> x.concat(y) 。concat是String对象的方法
        System.out.println(id.f("ice", " grass"));
        ic = String::trim; // 等价于 x -> x.trim()
        // 3. Class::staticMethod。同1，lambda所有参数传递到静态方法
        id = String::format; // 等价于 (x, y) -> String.format(x, y)
        System.out.println(id.f("hi %s", "blover"));
        // Java中，取静态字段也是使用“.”。至于::就是在构造lambda时引用已存在的方法

        IE ie;
        ie = B::new; // B::new引用了B的构造器方法。具体哪一个构造器取决于上下文
        ie = int[]::new; // 等价于 x -> new int[x]
        ie.f(0);

        // B.Inner inr = new B.Inner(); // 错误。static方法中，不能定义非static类对象
        @SuppressWarnings("unused")
        B.staticInner inr = new B.staticInner(); // static class

        int x = 123;
        // 局部内部类
        @SuppressWarnings("unused")
        class T { // 不能添加访问说明符
            private int i; 
            public void f() {
                new Timer(1000, event -> {
                    System.out.println(x); // x必须是事实最终变量
                }).start();
            }
            // 编译器检测到对局部变量的访问，为每一个变量建立实例字段，并将局部变量复制到构造器，从而能初始化对应的字段
            // final int val$x;
            // T(x);
        }
        // 当离开作用域后，变量x消失，但是类对象在异步调用时仍能正常工作。
        new T().f();

        // 匿名内部类，new SuperType(){...}，SuperType可以是已存在的接口或类
        // 1.创建一个匿名类对象，实现一个接口
        IA iia = new IA() {
            public int f1() {return 11;}
        };
        System.out.println(iia.f1() + " " + iia.f2());
        // 2.创建一个匿名类对象，继承一个类
        var aa = new A() { // aa引用了一个匿名类对象，而不是A类
            int w;
            {w = x;} // 匿名类不能有构造器，不过可以提供一个初始化块
        };
        System.out.println(aa.f1() + " " + aa.w); // 使用var声明aa，才能访问到w。A aa肯定是访问不了的

        ArrayList<Integer> list = new ArrayList<Integer>(){{add(1); add(2);}}; // 双括号初始化，一种技巧
        for(var i: list) System.out.printf(i + " ");
    }
}

// 可以将接口看做没有实例字段的抽象类
// 源文件中，公开类和公开接口只能有一个
interface IA {
    int NUM = 1; // 不能包含实例字段，但是可以有常量
    int f1(); // (抽象方法)
    default int f2() { // 可以提供一个默认实现，称为默认方法(非抽象方法)
        return NUM + f1(); // 默认方法可以调其他方法
    } // 加入新的默认方法，不会影响原先正常工作的代码
    // 1.如果超类提供了签名相同的方法，则默认方法被忽略
    // 2.类实现的两个接口如果具有相同签名的方法，则必须都是抽象方法
}

interface IB {
    int f1(); // 接口所有方法自动是public。*实现时也必须声明为public*
    int f3();
}

// 类可以实现多个接口，而不能继承多个抽象类，这正是他们的最大区别
class A implements IA, IB {

    // 实现接口
    public int f1() { // 同时实现IA IB的f1()
        return 1;
    }
    public int f3() {
        return 3;
    }
}

class B implements Comparable<B>, Cloneable {
    int id;
    B(int _id) {
        id = _id;
    }
    public int compareTo(B b) { // 实现Comparable接口。接受的参数为B
        return id - b.id;
    }

    String name;
    Date date;
    // Cloneable是标记型接口，不含任何方法，唯一作用是支持 B instanceof Cloneable 这样的类型查询
    // 重新定义clone为public
    @Override
    public B clone() throws CloneNotSupportedException { // 当执行clone的某个对象没有实现Cloneable，会抛出异常
        B cloned = (B) super.clone(); // Object的clone默认对类对象进行浅拷贝
        // String没有实现clone，这是因为String对象是不可变的，我们拷贝其引用即可
        cloned.date = (Date) date.clone(); // Date是可变对象，已经实现了clone
        return cloned;
    }

    public static void repeatMessage(int value) {
        // Java中，lambda表示式就是闭包，可以捕获*外围作用域*中变量的值
        // 在lambda表达式中，只能引用值不会改变的变量。实际上，捕获的变量必须是*事实最终变量*，初始化后就不会为其再赋新值
        // 下面三个value--都会使编译不通过
        // value--;
        new Timer(1000, event -> { // 函数式接口ActionEvent
            // value--;
            System.out.println(value);
        }).start();
        // value--;
    }

    public class Inner {
        String s = name;
        static int num;
    }
    static public class staticInner {
        // String s = name; // 静态内部类不能引用外围类的非静态字段
        String s; // 如果不需要引用任何外围类字段，推荐用static class
    }
}

@FunctionalInterface // 使用注解，指明是函数式接口
interface IC {
    void f(String s);

    static void fs() {} // 接口可以包含静态方法。这很少用到
}

interface ID {
    String f(String s1, String s2);
}

interface IE {
    void f(int x);
}